Skip to content
This repository was archived by the owner on Sep 26, 2024. It is now read-only.

Use authorization code flow with PKCE when a client secret is specified#67

Merged
N-lson merged 27 commits into
mainfrom
nelson/pkce
Apr 20, 2022
Merged

Use authorization code flow with PKCE when a client secret is specified#67
N-lson merged 27 commits into
mainfrom
nelson/pkce

Conversation

@N-lson

@N-lson N-lson commented Mar 17, 2022

Copy link
Copy Markdown
Contributor

Relates to OctopusDeploy/Issues#7141

Manually tested with:

  • Okta - on initial sign-in, didn't redirect me back to Octopus. had to manually re-open, and on second sign-in was signed into Octopus. I think this is because I then signed into Okta using Google so it can't handle redirecting on a double SSO. Both implicit flow and auth code flow work with this branch, meaning any instances currently using OAuth shouldn't be affected until they add a client secret.
  • Google - no issues
  • Azure AD - no issues. the docs say to use a https endpoint and a LetsEncrypt SSL cert, but I tested using http and didn't have a problem signing in . might just be required to set up the logout URL correctly since that requires a https endpoint.

Also comes with some docs changes OctopusDeploy/docs#1496

Testing

Terraform files for creating an app registration in Okta are available on the nelson/terraform branch. You can follow the instructions in our docs for creating a dev Okta instance.

The variables are:

  1. org_name -> The name of the instance eg. dev-3219475 (don't need to add -admin)
  2. api_token -> The API token generated in Security -> API in the Okta portal
  3. redirect_uri -> Would be http://localhost:8065/api/users/authenticatedToken/Okta on a default local server instance

@N-lson N-lson marked this pull request as ready for review March 30, 2022 00:05
@N-lson N-lson self-assigned this Mar 30, 2022
@N-lson N-lson requested a review from a team March 30, 2022 00:27
Comment thread source/Server.OpenIDConnect.Common/Infrastructure/Pkce.cs

@zentron zentron left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple little requests around code cleanup and a rename of the SessionId to RequestId but otherwise its looking pretty good.

Comment thread source/Server.OctopusID/Configuration/OctopusIDConfigureCommands.cs
Comment thread source/Server.OpenIDConnect.Common/Web/LoginStateWithSessionId.cs Outdated
Comment thread source/Server.OpenIDConnect.Common/Web/UserAuthenticatedPkceAction.cs Outdated
var stateFromRequest = JsonConvert.DeserializeObject<LoginStateWithSessionId>(state);
var response = await RequestAuthToken(code, redirectUri, stateFromRequest!.SessionId);

// Step 1: Try and get all of the details from the request making sure there are no errors passed back from the external identity provider

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im guessing this is a bit of C+P from what was in the original UserAuthenticatedAction method. is there anything here that we have in common that can be extracted out?

return loginFailed.Response(message);
}

IResultFromExtension<IUser> GetOrCreateUser(UserResource userResource, string[] groups, CancellationToken cancellationToken)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, is there much in here which is really different from the existing class?

@N-lson N-lson requested a review from zentron April 11, 2022 05:04

namespace Octopus.Server.Extensibility.Authentication.OpenIDConnect.Common.Web
{
public class AuthServerResponseHandler<TAuthTokenHandler> where TAuthTokenHandler : IAuthTokenHandler

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically this either handles a request (in the case of the implicit flow) or a response (in the case of auth code flow) from the auth server but I went with naming it ResponseHandler.


public async Task<ClaimsPrincipalContainer> GetDetailsFromRequestEnsuringNoErrors(IDictionary<string, string?> requestForm)
{
var principalContainer = await authTokenHandler.GetPrincipalAsync(requestForm, out _);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer use the out var for the state - could also remove it from the method itself?

var octoResponse = UserAuthenticatedValidator.Redirect.Response(state.RedirectAfterLoginTo)
.WithHeader("Expires", new[] { DateTime.UtcNow.AddYears(1).ToString("R", DateTimeFormatInfo.InvariantInfo) })
.WithCookie(new OctoCookie(UserAuthConstants.OctopusStateCookieName, Guid.NewGuid().ToString()) { HttpOnly = true, Secure = false, Expires = DateTimeOffset.MinValue })
.WithCookie(new OctoCookie(UserAuthConstants.OctopusNonceCookieName, Guid.NewGuid().ToString()) {HttpOnly = true, Secure = false, Expires = DateTimeOffset.MinValue});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm guessing setting these cookies just resets them to some value so they can't be re-used?


public async Task<IOctoResponseProvider> ExecuteAsync(IOctoRequest request)
{
// Step 1: Try and get all of the details from the request making sure there are no errors passed back from the external identity provider

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed all the comments. I think some of the method names are very clear, others maybe it would be useful to keep the comment?

{
await mediator.Do(
new PutBlobCommand(ConfigurationStore.ConfigurationSettingsName, requestId, JsonSerializer.SerializeToUtf8Bytes(blob)),
new CancellationToken());

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is new CancellationToken ok here?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cant see many places where we might already have a token so could you perhaps just wrap the code as high as it makes sense with a

   using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1));

and pass it around where necessary

@zentron zentron left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comment around the cancellation token but once that is fixed and this all still works then feel free to merge :)
Thanks for the additional time spent 👍

{
await mediator.Do(
new PutBlobCommand(ConfigurationStore.ConfigurationSettingsName, requestId, JsonSerializer.SerializeToUtf8Bytes(blob)),
new CancellationToken());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cant see many places where we might already have a token so could you perhaps just wrap the code as high as it makes sense with a

   using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(1));

and pass it around where necessary

@N-lson N-lson merged commit f436506 into main Apr 20, 2022
@N-lson N-lson deleted the nelson/pkce branch April 20, 2022 22:55
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants